package com.ar4j.spring;

import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;

import com.ar4j.ActiveRecord;
import com.ar4j.ActiveRecordContext;
import com.ar4j.IActiveRecordFactory;
import com.ar4j.NamedSingletonActiveRecordFactory;
import com.ar4j.ActiveRecordJsonWrapper;
import com.ar4j.sql.INamedQuerySource;

/**
 * A wrapper for the ActiveRecordFactory singleton, that can be a spring bean. 
 * Supports the same operations as ActiverRecordFactory, but performs an additional
 * step of filling in any new record's context with a data source, named query source (if present),
 * and a platform transaction manager, as well as other settings.
 */
@Component
public class SpringActiveRecordFactory implements IActiveRecordFactory, InitializingBean {
  
  @Autowired
  private DataSource dataSource;
  
  @Autowired(required = false)
  private PlatformTransactionManager platformTransactionManager;
  
  @Autowired(required = false)
  private INamedQuerySource namedQuerySource;
  
  private String factoryName;
  private ActiveRecordContext prototypeContext;

  /**
   * @return an active record factory for the current wrapper's factory name
   */
  public NamedSingletonActiveRecordFactory getFactory() {
    return NamedSingletonActiveRecordFactory.getFactory(factoryName);
  }

  @Override
  public <E extends ActiveRecord<E>> E getActiveRecord(Class<E> domainClass) {
    E out = getFactory().getActiveRecord(domainClass);
    out.getContext().setValuesFrom(prototypeContext);
    return out;
  }
  
  @Override
  public <E extends ActiveRecord<E>> E getActiveRecord(Class<E> domainClass, ActiveRecordJsonWrapper wrapper) {
    E out = getFactory().getActiveRecord(domainClass, wrapper);
    out.getContext().setValuesFrom(prototypeContext);
    return out;
  }
  
  @Override
  public <E extends ActiveRecord<E>> E getActiveRecord(E prototype) {
    E out = getFactory().getActiveRecord(prototype);
    out.getContext().setValuesFrom(prototypeContext);
    return out;
  }
  
  @Override
  public <E extends ActiveRecord<E>> E getActiveRecord(Class<E> domainClass, Map<String, Object> values) {
    E out = getFactory().getActiveRecord(domainClass, values);
    out.getContext().setValuesFrom(prototypeContext);
    return out;
  }
  
  @Override
  public void afterPropertiesSet() throws Exception {
    
    // set default factory name
    if(factoryName == null) {
      factoryName = NamedSingletonActiveRecordFactory.DEFAULT_FACTORY_NAME;
    }
    
    // create the prototype context
    prototypeContext = new ActiveRecordContext()
      .setDataSource(dataSource)
      .setNamedQuerySource(namedQuerySource)
      .setPlatformTransactionManager(getPlatformTransactionManager());
    
  }

  public DataSource getDataSource() {
    return dataSource;
  }

  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
  }

  public PlatformTransactionManager getPlatformTransactionManager() {
    return platformTransactionManager;
  }

  public void setPlatformTransactionManager(PlatformTransactionManager platformTransactionManager) {
    this.platformTransactionManager = platformTransactionManager;
  }

  public INamedQuerySource getNamedQuerySource() {
    return namedQuerySource;
  }

  public void setNamedQuerySource(INamedQuerySource namedQuerySource) {
    this.namedQuerySource = namedQuerySource;
  }

  public String getFactoryName() {
    return factoryName;
  }

  public void setFactoryName(String factoryName) {
    this.factoryName = factoryName;
  }
  
  @Override
  public String toString() {
    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
  }
  
}
